using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;


namespace KunURP
{
    /// <summary>
    /// Generate halton sequence.
    /// code from unity post-processing stack.
    /// </summary>
    public class HaltonSequence {
        public int radix = 3;
        private int storedIndex = 0;
        public float Get() {
            float result = 0f;
            float fraction = 1f / (float)radix;
            int index = storedIndex;
            while (index > 0) {
                result += (float)(index % radix) * fraction;

                index /= radix;
                fraction /= (float)radix;
            }
            storedIndex++;
            return result;
        }
    }

    public class VolumeCloud : ScriptableRendererFeature
    {
        [System.Serializable]
        public class Setting
        {
            public RenderPassEvent passEvent = RenderPassEvent.AfterRenderingTransparents;//默认插到透明完成后
            public Material material;
        }

        public Setting m_Setting = new Setting();

        class CustomRenderPass : ScriptableRenderPass
        {
            public const int MaxLevel = 16;

            public Material material;

            FilterMode passFilterMode{ get; set; } //图像的模式
            RenderTargetIdentifier passSource{ get; set; } //源图像,目标图像
            string passTag;

            private RenderTexture undersampleBuffer;
            private RenderTexture downsampledDepth;
            private HaltonSequence sequence = new HaltonSequence() { radix = 3 };
            private RenderTexture[] fullBuffer;
            private int fullBufferIndex;

            private Matrix4x4 prevV;

            static class ShaderIDs
            {
                internal static readonly int SourceBuffer = Shader.PropertyToID("_SourceBuffer");
                internal static readonly int UnderSampleBuffer = Shader.PropertyToID("_UnderSampleBuffer");
                internal static readonly int CloudTex = Shader.PropertyToID("_CloudTex");
                internal static readonly int ProjectionExtents = Shader.PropertyToID("_ProjectionExtents");
                internal static readonly int RaymarchOffset = Shader.PropertyToID("_RaymarchOffset");
                internal static readonly int TexelSize = Shader.PropertyToID("_TexelSize");
            }

            public CustomRenderPass(string tag)
            {
                this.passTag = tag;
            }

            public void Setup(RenderTargetIdentifier passSource)
            {
                this.passSource = passSource;
            }
            
            public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
            {
                CommandBuffer cmd = CommandBufferPool.Get(this.passTag);
                RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;
                Camera camera = renderingData.cameraData.camera;
                int width = camera.pixelWidth;
                int height = camera.pixelHeight;

                EnsureArray(ref fullBuffer, 2);
                EnsureRenderTarget(ref fullBuffer[0], width, height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear);
                EnsureRenderTarget(ref fullBuffer[1], width, height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear);
                EnsureRenderTarget(ref undersampleBuffer, width , height, RenderTextureFormat.ARGBFloat, FilterMode.Bilinear);

                fullBufferIndex = (fullBufferIndex + 1) % 2;

                this.material.SetVector(ShaderIDs.ProjectionExtents, camera.GetProjectionExtents());
                this.material.SetFloat(ShaderIDs.RaymarchOffset, 0.2f);
                this.material.SetVector(ShaderIDs.TexelSize, undersampleBuffer.texelSize);
                cmd.GetTemporaryRT(ShaderIDs.SourceBuffer, opaqueDesc);
                cmd.GetTemporaryRT(ShaderIDs.UnderSampleBuffer, opaqueDesc);
                cmd.CopyTexture(this.passSource, ShaderIDs.SourceBuffer);
                cmd.Blit(null, undersampleBuffer, material, 0);
                // this.material.SetTexture(ShaderIDs.CloudTex, );
                this.material.SetTexture("_UndersampleCloudTex", undersampleBuffer);
                this.material.SetMatrix("_PrevVP", GL.GetGPUProjectionMatrix(camera.projectionMatrix,false) * prevV);
                this.material.SetVector("_ProjectionExtents", camera.GetProjectionExtents());
                cmd.Blit(fullBuffer[fullBufferIndex], fullBuffer[fullBufferIndex ^ 1], material, 1);
                this.material.SetTexture("_CloudTex", fullBuffer[fullBufferIndex ^ 1]);
                cmd.Blit(ShaderIDs.SourceBuffer, this.passSource, material, 2);
                // cmd.Blit(ShaderIDs.UnderSampleBuffer, this.passSource);
                cmd.ReleaseTemporaryRT(ShaderIDs.SourceBuffer);
                cmd.ReleaseTemporaryRT(ShaderIDs.UnderSampleBuffer);
                context.ExecuteCommandBuffer(cmd);
                CommandBufferPool.Release(cmd);
                this.prevV = camera.worldToCameraMatrix;
            }

            public bool EnsureRenderTarget(ref RenderTexture rt, int width, int height, RenderTextureFormat format, FilterMode filterMode, TextureWrapMode wrapMode = TextureWrapMode.Clamp, bool randomWrite = false, bool useMipmap = false, int depthBits = 0, int antiAliasing = 1)
            {
                if (rt != null && (rt.width != width || rt.height != height || rt.format != format || rt.filterMode != filterMode || rt.enableRandomWrite != randomWrite || rt.wrapMode != wrapMode || rt.antiAliasing != antiAliasing || rt.useMipMap != useMipmap))
                {
                    rt.Release();
                    rt = null;
                }
                if (rt == null)
                {
                    rt = new RenderTexture(width, height, depthBits, format, RenderTextureReadWrite.Default);
                    rt.antiAliasing = antiAliasing;
                    rt.useMipMap = useMipmap;
                    rt.filterMode = filterMode;
                    rt.enableRandomWrite = randomWrite;
                    rt.wrapMode = wrapMode;
                    rt.Create();
                    return true;// new target
                }
                return false;// same target
            }

            public void EnsureArray<T>(ref T[] array, int size, T initialValue = default(T))
            {
                if (array == null || array.Length != size)
                {
                    array = new T[size];
                    for (int i = 0; i != size; i++)
                        array[i] = initialValue;
                }
            }
        }

        CustomRenderPass m_ScriptablePass;

        public override void Create()
        {
            this.m_ScriptablePass = new CustomRenderPass(name);
            this.m_ScriptablePass.renderPassEvent = this.m_Setting.passEvent;
            this.m_ScriptablePass.material = this.m_Setting.material;
        }

        // Here you can inject one or multiple render passes in the renderer.
        // This method is called when setting up the renderer once per-camera.
        public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
        {
            if (this.m_Setting.material != null)
            {
                this.m_ScriptablePass.Setup(renderer.cameraColorTarget);
                renderer.EnqueuePass(this.m_ScriptablePass);
            }
        }
    }

}
